package com.lp.multi.atomic;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * @author: lp
 * @create: 2021-06-29 9:45
 * @description: 解决ABA问题
 * 1.泛型是Integer是的坑，值不能超过128原理:
 *   (1)Integer的值在-127~128直接，取的是Integer缓存，值相同，对象都是同一个；不在这个范围内的自动包装会new一个新对象
 *   (2)compareAndSet方法参数expectedReference传的不是包装类，会重新new一个对象，CAS比较比较的是引用
 *      地址： expectedReference == current.reference，修改不会成功.
 *   (3)版本号不会有这个问题，参数是int类型,stamp也是int类型
 * 2.CAS原理:AtomicInteger+1原理
 *   (1)使用Unsafe获取AtomicInteger的VALUE属性的地址偏移量;
 *   (2)使用while循环自旋，每次重新用Unsafe根据VALUE地址偏移量获取内存值，重新判断VALUE是否和期望值一样，并进行修改
 *   (3)CAS调用的是本地方法，靠的是CPU的原子指令，基于汇编指令cmpxchg（Intel x86）实现，效率比较高
 *
 * 3.CAS适合场景：
 *   (1)CAS 适合简单对象的操作，比如布尔值、整型值等；
 *   (2)CAS 适合冲突较少的情况，如果太多线程在同时自旋，那么长时间循环会导致 CPU 开销很大；
 *
 * 3.CAS锁优点:
 *   (1)由于CAS是非阻塞的，可避免死锁，线程间的互相影响非常小。
 *   (2)没有锁竞争带来的系统开销，也没有线程间频繁调度的开销。
 *
 * 4.CAS锁缺点:
 *   (1)CPU开销过大:高并发使用CAS更新值，如果许多线程反复尝试更新某一个变量，却又一直更新不成功，循环往复，会给CPU带来很到的压力。
 *   (2)不能保证代码块的原子性：CAS机制所保证的知识一个变量的原子性操作，而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新，就不得不使用synchronized了。
 * 　(3)ABA问题:A线程把value=3改为value=5,A线程再把value=5改为value=3;B线程任务value的值没有变。
 * 4.解决ABA问题:AtomicStampedReference增加版本号控制
 */
public class CasAbaTest {
    //注意：initialRef的值不能超过128，超过的话，修改时的参数会new一个新的对象，compareAndSet方法CAS比较的是地址引用,修改不了
    private static AtomicStampedReference<Integer> reference = new AtomicStampedReference<Integer>(1,1);

    public static void main(String[] args) {
        int stamp = reference.getStamp();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"第一次修改前stamp="+stamp);
            System.out.println(Thread.currentThread().getName()+"修改："+reference.compareAndSet(1, 10, stamp, stamp + 1));
            System.out.println(Thread.currentThread().getName()+"第一次修改后stamp="+reference.getStamp());
            System.out.println(Thread.currentThread().getName()+"修改后的值="+reference.getReference());
            System.out.println("----------------------------------------------------------------------");
        },"a").start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int stamp2 = reference.getStamp();
            //值被修改了
            System.out.println(Thread.currentThread().getName()+"第1次修改前stamp="+stamp2);
            System.out.println(Thread.currentThread().getName()+"修改："+reference.compareAndSet(1, 30, stamp2, stamp2 + 1));
            System.out.println(Thread.currentThread().getName()+"第1次修改后stamp="+reference.getStamp());
            System.out.println(Thread.currentThread().getName()+"修改后的值="+reference.getReference());
            System.out.println("*********************第1次end***********************************");

            System.out.println(Thread.currentThread().getName()+"第2次修改前stamp="+stamp);
            //版本号被修改了
            System.out.println(Thread.currentThread().getName()+"修改："+reference.compareAndSet(10, 30, stamp, stamp + 1));
            System.out.println(Thread.currentThread().getName()+"第2次修改后stamp="+reference.getStamp());
            System.out.println(Thread.currentThread().getName()+"修改后的值="+reference.getReference());
            System.out.println("********************第2次end************************************");

            //值和版本号都已经被a线程修改了
            //int stamp = reference.getStamp();
            System.out.println(Thread.currentThread().getName()+"第3次修改前stamp="+stamp);
            System.out.println(Thread.currentThread().getName()+"修改："+reference.compareAndSet(1, 30, stamp, stamp + 1));
            System.out.println(Thread.currentThread().getName()+"第3次修改后stamp="+reference.getStamp());
            System.out.println(Thread.currentThread().getName()+"修改后的值="+reference.getReference());
            System.out.println("*******************第3次end*************************************");
        },"b").start();
    }

}
